#ifndef SOS_FRAME_TABLE_H
#define SOS_FRAME_TABLE_H

#include <sel4/sel4.h>
#include <cspace/cspace.h>

#include "pagetable.h"
#include "process.h"

#define FRAME_TABLE_SUCCESS     (0)
#define FRAME_TABLE_OOM         (1)
#define FRAME_TABLE_SHARE_FAULT (2)
#define FRAME_TABLE_INVAL       (3)
#define FRAME_TABLE_SWAPRACE    (4)

/* Info relating a physical frame to a process. */
/* A frame_mapping should be sufficient to restore a frame from swap. */
typedef struct frame_mapping {
    sos_pcb *pcb;         /* SOS shadow PCB */
    seL4_Word proc_vaddr; /* Client virtual address, where the frame is mapped  */
    seL4_CPtr proc_cap;   /* Client cap, used to map and unmap into proc vspace */
    // struct frame_mapping *next; /* Form a linked list for shared memory */
} frame_mapping;

typedef struct frame_table_entry {
    /* SmallPageObject cap mapping frame into SOS window */
    seL4_ARM_Page page;

    /* All the information relating to attached client */
    frame_mapping mapping;

    /* Indicates whether or not this frame is currently in the swap queue */
    int swappable;

    /* The 'second-chance' bit used by swap policy */
    int referenced;

    /* Allocated frames form a doubly-linked list, used for swap policy */
    struct frame_table_entry *next;
    struct frame_table_entry *prev;
} frame_table_entry;

typedef frame_table_entry *frame_table;

/**
 * A callback function provided by the caller of frame_alloc, executed once
 * a response is received.
 * @param[in] vaddr The SOS virtual address of the new frame. Null if OOM.
 * @param[in] cookie  The pointer passed into frame_alloc, unmodified.
 */
typedef void (*frame_alloc_cb)(seL4_Word vaddr, void *cookie);

/**
 * A callback function provided by the caller of frame_swapin, executed once
 * a response is received.
 * @param[in] err The error code for the operation.
 * @param[in] cookie The cookie pointer passed into frame_swapin, unmodified.
 */
typedef void (*frame_swapin_cb)(int err, void *cookie);

/**
 * Initialise the frame table. This can only be done once - it leaks frames.
 * @pre Frame table has not been initialised.
 * @return FRAME_TABLE_SUCCESS on success, FRAME_TABLE_OOM if out of memory.
 */
int frame_table_init(void);

/**
 * Allocate a single frame and map it into SOS' address space.
 * If no frames are available, a swappable frame may be evicted.
 * This is an asynchronous function - it may require swapping to disk.
 * XXX Explain when callback will be called
 * @pre Frame table has been initialised
 * @param cb A callback function that will be run on completion.
 * @param data Abstract data that will be returned to cb, unaccessed
 * XXX Explain / decide on return value conventions
 */
int frame_alloc(frame_alloc_cb cb, void *data);

/**
 * Copy the capability for a frame into a process cspace with the given rights.
 * The frame is then added to the tail of the swap queue, i.e. it is swappable.
 * @pre Frame table has been initialised, frame at vaddr has been allocated
 * @param sos_vaddr Pointer to a frame in the SOS mapped window
 * @param pcb Pointer to process PCB
 * @return FRAME_TABLE_SUCCESS on success, FRAME_TABLE_SHARE_FAULT if the
 *         frame is already granted to another process.
 */
int frame_grant(seL4_Word sos_vaddr, sos_pcb *pcb, seL4_Word proc_vaddr);

/**
 * Destroy the process capability for a given frame. This does not free the
 * frame - the caller should use frame_free afterwards.
 * If no more capabilities are held, i.e. frame is accessible only to SOS, 
 * the frame is removed from the swap queue.
 * @pre Frame table has been initialised, frame at vaddr has been allocated
 * @param vaddr Pointer to a frame in the SOS mapped window
 * @param proc_cspace Pointer to a process's cspace
 * @param cap The capability to be destroyed
 * @return FRAME_TABLE_SUCCESS on success
 */
int frame_ungrant(seL4_Word sos_vaddr, cspace_t *proc_cspace, seL4_CPtr cap);

/**
 * Swap policy requires that frames be unmapped for a while, before they are
 * actually swapped to disk. If the pager calls frame_referenced during this
 * stage, the frame will be mapped back into the process synchronously.
 * @param frame_number The frame number to be referenced.
 * @return FRAME_TABLE_SUCCESS on success, else nonzero
 */
int frame_referenced(seL4_Word frame_number);

/**
 * Prevent a frame from being swapped.
 */
int frame_pin(seL4_Word sos_vaddr);

/**
 * Allow a pinned frame to be swapped again, i.e. revert a pin.
 */
int frame_release(seL4_Word sos_vaddr);

/**
 * Unmap the frame from SOS, destroy the frame object, and return the
 * physical range to ut.
 * @pre Frame table has been initialised.
 * @pre sos_vaddr points to a frame in the SOS mapped window.
 * @param sos_vaddr for the frame to be freed.
 * @return FRAME_TABLE_SUCCESS on success, panic on failure.
 */
int frame_free(seL4_Word sos_vaddr);

/**
 * Request the restoration of a swapped-out frame.
 * This is an asynchronous operation.
 * @pre Frame has been swapped out with number swap_number
 * @param swap_number The swap index for the desired frame.
 * @param cb Callback to be run from the ready-queue after success.
 * @param cookie Data pointer to be passed to cb, unmodified.
 * @return FRAME_TABLE_SUCCESS on success, else nonzero.
 */
int frame_swapin(seL4_Word swap_number, frame_swapin_cb cb, void *cookie);

/**
 * Obtain the process cap for a given vaddr. Useful for unmapping / ungranting.
 * @pre Frame table has been initialised.
 * @param sos_vaddr points to a frame in the SOS mapped window.
 * @param proc_cap Pointer to memory that will store the capability
 * @return FRAME_TABLE_SUCCESS on success,
 */
int frame_process_cap(seL4_Word sos_vaddr, sos_pcb *pcb, seL4_CPtr *proc_cap);

/**
 *  Translate a SOS virtual address to a frame number.
 *  @pre Frame table has been initialised.
 *  @param sos_vaddr a page-aligned address within the SOS mapped window.
 *  @return The frame number corresponding to the given vaddr.
 *  @post Frame table has not been modified by this function.
 */
seL4_Word frame_translate_vaddr(seL4_Word sos_vaddr);

/**
 *  Translate a frame number to a SOS virtual address.
 *  @pre Frame table has been initialised.
 *  @param frame_number a frame number received from frame_translate_vaddr
 *  @return The SOS virtual address corresponding to frame_number.
 *  @post Frame table has not been modified by this function.
 */
seL4_Word frame_translate_number(seL4_Word frame_number);

/**
 * Flush the instruction cache for the frame at frame_number.
 */
int frame_flush_icache(seL4_Word frame_number);

#endif // SOS_FRAME_TABLE_H
